﻿using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System;
using Newtonsoft.Json;

namespace Utility.DataSystem
{
	[JsonObject (MemberSerialization = MemberSerialization.OptIn)]
	[Serializable]
	public struct DataValueType : IEquatable<DataValueType>, ISerializationCallbackReceiver
	{
		public enum Categorys
		{
			[ShowName ("无")]
			Null = 0,

			[ShowName ("数字")]
			Number,
			[ShowName ("布尔")]
			Bool,
			[ShowName ("枚举")]
			Enum,
			[ShowName ("字符串")]
			String,
		}

		#region static
		public static DataValueType Null { get; } = new DataValueType (Categorys.Null);
		public static DataValueType Number { get; } = new DataValueType (Categorys.Number);
		public static DataValueType Bool { get; } = new DataValueType (Categorys.Bool);
		public static DataValueType String { get; } = new DataValueType (Categorys.String);
		public static DataValueType Enum (Type enumType) => new DataValueType (Categorys.Enum, enumType);
		public static DataValueType Enum<TEnum> () => new DataValueType (Categorys.Enum, typeof (TEnum));
		#endregion

		public DataValueType (Categorys category, Type type = null)
		{
			this.category = category;
			switch (category)
			{
				default:
				case Categorys.Null:
					this.type = null;
					break;
				case Categorys.Number:
					this.type = typeof (decimal);
					break;
				case Categorys.Bool:
					this.type = typeof (bool);
					break;
				case Categorys.Enum:
					this.type = type == null || type.IsEnum ? type : throw new Exception ($"Type is {type}, which must be enum");
					break;
				case Categorys.String:
					this.type = typeof (string);
					break;
			}
			_typeStr = null;
		}

		[JsonProperty]
		public Categorys category;
		[JsonProperty]
		public Type type;


		public bool IsNull => category == Categorys.Null;
		public bool IsNumber => category == Categorys.Number;
		public bool IsBool => category == Categorys.Bool;
		public bool IsEnum => category == Categorys.Enum;
		public bool IsString => category == Categorys.String;
		public bool IsType (Type type) => this.type == type;
		public bool IsType<T> () => this.type == typeof (T);

		#region serialization
		[SerializeField] string _typeStr;
		void ISerializationCallbackReceiver.OnAfterDeserialize ()
		{
			type = string.IsNullOrWhiteSpace (_typeStr) ? null : Type.GetType (_typeStr, false);
			category = type == null ? Categorys.Null : category;

			_typeStr = null;
		}

		void ISerializationCallbackReceiver.OnBeforeSerialize ()
		{
			_typeStr = type?.AssemblyQualifiedName;
		}
		#endregion
		#region operator
		public bool Equals (DataValueType other)
		{
			return type == other.type;
		}

		public override bool Equals (object obj)
		{
			return obj is DataValueType t ? Equals (t) : false;
		}

		public override int GetHashCode ()
		{
			return 34944597 + EqualityComparer<Type>.Default.GetHashCode (type);
		}

		public static bool operator == (DataValueType left, DataValueType right)
		{
			return left.Equals (right);
		}

		public static bool operator != (DataValueType left, DataValueType right)
		{
			return !(left == right);
		}
		#endregion
	}

	[JsonObject (MemberSerialization = MemberSerialization.OptIn)]
	[Serializable]
	public struct DataValue : IEquatable<DataValue>, ISerializationCallbackReceiver
	{
		#region static
		public static DataValue Parse (object val)
		{
			if (val == null)
			{
				return default;
			}
			else if (val is decimal _decimal)
			{
				return new DataValue (_decimal);
			}
			else if (val is decimal?)
			{
				return new DataValue ((decimal?)val);
			}
			else if (val is int _int)
			{
				return new DataValue (_int);
			}
			else if (val is int?)
			{
				return new DataValue ((int?)val);
			}
			else if (val is float _float)
			{
				return new DataValue (_float);
			}
			else if (val is float?)
			{
				return new DataValue ((float?)val);
			}
			else if (val is bool _bool)
			{
				return new DataValue (_bool);
			}
			else if (val is bool?)
			{
				return new DataValue ((bool?)val);
			}
			else if (val is string _string)
			{
				return new DataValue (_string);
			}
			else if (val is Enum _enum)
			{
				return new DataValue (_enum);
			}
			else
			{
				throw new NotSupportedException ($"does not support type: {val.GetType ()}");
			}
		}

		public static DataValue Zero => new DataValue (0);
		public static DataValue Null => default;
		#endregion
		[SerializeField]
		[JsonProperty]
		public DataValueType dataValueType;
		[JsonProperty]
		decimal numberVal;
		[JsonProperty]
		object objVal;

		bool boolVal
		{
			get => numberVal > 0;
			set => numberVal = value ? 1 : 0;
		}

		string stringVal
		{
			get => (string)objVal;
			set => objVal = value;
		}

		Enum enumVal
		{
			get => (Enum)objVal;
			set => objVal = value;
		}

		#region value getter
		public decimal AsNumber (decimal defaultValue = default)
		{
			return AsNNumber () ?? defaultValue;
		}

		public decimal? AsNNumber ()
		{
			if (IsNumber)
			{
				return numberVal;
			}
			else if (IsNull)
			{
				return null;
			}
			else
			{
				throw new NotSupportedException ("DataValue is not number type");
			}
		}

		public int AsInt (int defaultValue = default)
		{
			return AsNInt () ?? defaultValue;
		}

		public int? AsNInt ()
		{
			if (IsNumber)
			{
				return (int)numberVal;
			}
			else if (IsNull)
			{
				return null;
			}
			else
			{
				throw new NotSupportedException ("DataValue is not number type");
			}
		}

		public float AsFloat (float defaultValue = default)
		{
			return AsNFloat () ?? defaultValue;
		}

		public float? AsNFloat ()
		{
			if (IsNumber)
			{
				return (float)numberVal;
			}
			else if (IsNull)
			{
				return null;
			}
			else
			{
				throw new NotSupportedException ("DataValue is not number type");
			}
		}

		public bool AsBool (bool defaultValue = default)
		{
			return AsNBool () ?? defaultValue;
		}

		public bool? AsNBool ()
		{
			if (IsBool)
			{
				return boolVal;
			}
			else if (IsNull)
			{
				return null;
			}
			else
			{
				throw new NotSupportedException ("DataValue is not bool type");
			}
		}

		public string AsString (string defaultValue = default)
		{
			if (IsString)
			{
				return stringVal;
			}
			else if (IsNull)
			{
				return defaultValue;
			}
			else
			{
				throw new NotSupportedException ("DataValue is not string type");
			}
		}

		public TEnum AsEnum<TEnum> (TEnum defaultValue = default)
			where TEnum : struct, Enum
		{
			return AsNEnum<TEnum> () ?? defaultValue;
		}

		public Enum AsEnum ()
		{
			if (IsEnum)
			{
				return enumVal;
			}
			else if (IsNull)
			{
				return null;
			}
			else
			{
				throw new NotSupportedException ($"DataValue is not enum type");
			}
		}

		public TEnum? AsNEnum<TEnum> ()
			where TEnum : struct, Enum
		{
			if (IsType (typeof (TEnum)))
			{
				return (TEnum)enumVal;
			}
			else if (IsNull)
			{
				return null;
			}
			else
			{
				throw new NotSupportedException ($"DataValue is not enum type: {typeof (TEnum)}");
			}
		}
		#endregion
		#region ctor
		//number
		public DataValue (decimal val) : this ()
		{
			numberVal = val;
			dataValueType = DataValueType.Number;
		}

		public DataValue (decimal? val) : this ()
		{
			if (val.HasValue)
			{
				numberVal = val.Value;
				dataValueType = DataValueType.Number;
			}
			else
			{
				numberVal = default;
				dataValueType = DataValueType.Null;
			}
		}

		public DataValue (int val) : this ((decimal)val) { }
		public DataValue (int? val) : this ((decimal?)val) { }
		public DataValue (float val) : this ((decimal)val) { }
		public DataValue (float? val) : this ((decimal?)val) { }
		public DataValue (double val) : this ((decimal)val) { }
		public DataValue (double? val) : this ((decimal?)val) { }


		//bool
		public DataValue (bool val) : this ()
		{
			boolVal = val;
			dataValueType = DataValueType.Bool;
		}
		public DataValue (bool? val) : this ()
		{
			if (val.HasValue)
			{
				boolVal = val.Value;
				dataValueType = DataValueType.Bool;
			}
			else
			{
				boolVal = default;
				dataValueType = DataValueType.Null;
			}
		}

		//enum
		public DataValue (Enum val) : this ()
		{
			if (val == null)
			{
				dataValueType = DataValueType.Null;
			}
			else
			{
				enumVal = val;
				dataValueType = DataValueType.Enum (val.GetType ());
			}
		}

		//string
		public DataValue (string val) : this ()
		{
			stringVal = val;
			dataValueType = DataValueType.String;
		}
		#endregion
		#region utility
		public bool HasValue => !dataValueType.IsNull;
		public bool IsNull => dataValueType.IsNull;
		public bool IsNumber => dataValueType.IsNumber;
		public bool IsBool => dataValueType.IsBool;
		public bool IsEnum => dataValueType.IsEnum;
		public bool IsString => dataValueType.IsString;
		public bool IsType (Type type) => dataValueType.type == type;
		public bool IsType<T> () => dataValueType.type == typeof (T);

		public override string ToString ()
		{
			switch (dataValueType.category)
			{
				case DataValueType.Categorys.Null:
					return "NullValue";
				case DataValueType.Categorys.Number:
					return numberVal.ToString ();
				case DataValueType.Categorys.Bool:
					return boolVal.ToString ();
				case DataValueType.Categorys.Enum:
					return objVal.ToString ();
				case DataValueType.Categorys.String:
					return stringVal;
			}
			return "Unknown";
		}

		public string ToString (string format)
		{
			if (IsNumber)
			{
				return numberVal.ToString (format);
			}
			else
			{
				return ToString ();
			}
		}

		public void Clamp (decimal? min, decimal? max)
		{
			if (IsNumber)
			{
				if (min.HasValue)
				{
					numberVal = min.Value > numberVal ? min.Value : numberVal;
				}

				if (max.HasValue)
				{
					numberVal = max.Value < numberVal ? max.Value : numberVal;
				}
			}
			else
			{
				throw new NotSupportedException ();
			}
		}

		public void Clamp (DataValue min, DataValue max)
		{
			if (IsNumber)
			{
				if (min.HasValue && !min.IsNumber)
				{
					throw new NotSupportedException ("MinValue is not number or null");
				}

				if (max.HasValue && !max.IsNumber)
				{
					throw new NotSupportedException ("MaxValue is not number or null");
				}

				Clamp (min.AsNNumber (), max.AsNNumber ());
			}
			else
			{
				throw new NotSupportedException ();
			}
		}

		public void AssertTypeMatch (DataValue b)
		{
			if (dataValueType.type != b.dataValueType.type)
			{
				throw new InvalidOperationException ($"data type mismatched, require: {dataValueType.type}, get {b.dataValueType.type}");
			}
		}

		public override bool Equals (object obj)
		{
			if (obj == null)
			{
				return IsNull;
			}
			return obj is DataValue value && Equals (value);
		}

		public bool Equals (DataValue other)
		{
			if (dataValueType != other.dataValueType)
			{
				return false;
			}
			switch (dataValueType.category)
			{
				case DataValueType.Categorys.Number:
					return numberVal == other.numberVal;
				case DataValueType.Categorys.Bool:
					return boolVal == other.boolVal;
				case DataValueType.Categorys.Enum:
				case DataValueType.Categorys.String:
					return EqualityComparer<object>.Default.Equals (objVal, other.objVal);
			}
			return true;
		}

		public override int GetHashCode ()
		{
			var hashCode = -1149821163;
			hashCode = hashCode * -1521134295 + dataValueType.GetHashCode ();
			switch (dataValueType.category)
			{
				case DataValueType.Categorys.Number:
					hashCode = hashCode * -1521134295 + numberVal.GetHashCode ();
					break;
				case DataValueType.Categorys.Bool:
					hashCode = hashCode * -1521134295 + boolVal.GetHashCode ();
					break;
				case DataValueType.Categorys.Enum:
				case DataValueType.Categorys.String:
					hashCode = hashCode * -1521134295 + EqualityComparer<object>.Default.GetHashCode (objVal);
					break;
			}
			return hashCode;
		}
		#endregion
		#region operator
		public static DataValue operator + (DataValue a, DataValue b)
		{
			if (a.IsNull)
			{
				return b;
			}
			if (b.IsNull)
			{
				return a;
			}
			a.AssertTypeMatch (b);
			if (a.IsNumber)
			{
				return new DataValue (a.numberVal + b.numberVal);
			}
			else if (a.IsString)
			{
				return new DataValue (a.stringVal + b.stringVal);
			}
			else
			{
				throw new NotSupportedException ();
			}
		}

		public static DataValue operator - (DataValue a, DataValue b)
		{
			if (a.IsNull)
			{
				return b;
			}
			if (b.IsNull)
			{
				return a;
			}
			a.AssertTypeMatch (b);
			if (a.IsNumber)
			{
				return new DataValue (a.numberVal - b.numberVal);
			}
			else
			{
				throw new NotSupportedException ();
			}
		}

		public static DataValue operator * (DataValue a, DataValue b)
		{
			if (a.IsNull)
			{
				return b;
			}
			if (b.IsNull)
			{
				return a;
			}

			a.AssertTypeMatch (b);
			if (a.IsNumber)
			{
				return new DataValue (a.numberVal * b.numberVal);
			}
			else
			{
				throw new NotSupportedException ();
			}
		}

		public static DataValue operator / (DataValue a, DataValue b)
		{
			if (a.IsNull)
			{
				return b;
			}
			if (b.IsNull)
			{
				return a;
			}
			a.AssertTypeMatch (b);
			if (a.IsNumber)
			{
				return new DataValue (a.numberVal / b.numberVal);
			}
			else
			{
				throw new NotSupportedException ();
			}
		}

		public static bool operator > (DataValue a, DataValue b)
		{
			if (a.IsNull)
			{
				return false;
			}
			if (b.IsNull)
			{
				return true;
			}
			a.AssertTypeMatch (b);
			if (a.IsNumber)
			{
				return a.numberVal > b.numberVal;
			}
			else
			{
				throw new NotSupportedException ();
			}
		}

		public static bool operator < (DataValue a, DataValue b)
		{
			if (a.IsNull)
			{
				return false;
			}
			if (b.IsNull)
			{
				return true;
			}
			a.AssertTypeMatch (b);
			if (a.IsNumber)
			{
				return a.numberVal < b.numberVal;
			}
			else
			{
				throw new NotSupportedException ();
			}
		}

		public static DataValue operator & (DataValue a, DataValue b)
		{
			if (a.IsNull)
			{
				return b;
			}
			if (b.IsNull)
			{
				return a;
			}
			a.AssertTypeMatch (b);
			if (a.IsNumber)
			{
				return new DataValue (a.AsNInt () & b.AsNInt ());
			}
			else if (a.IsBool)
			{
				return new DataValue (a.boolVal & b.boolVal);
			}
			else
			{
				throw new NotSupportedException ();
			}
		}

		public static DataValue operator | (DataValue a, DataValue b)
		{
			if (a.IsNull)
			{
				return b;
			}
			if (b.IsNull)
			{
				return a;
			}
			a.AssertTypeMatch (b);
			if (a.IsNumber)
			{
				return new DataValue (a.AsNInt () | b.AsNInt ());
			}
			else if (a.IsBool)
			{
				return new DataValue (a.boolVal | b.boolVal);
			}
			else
			{
				throw new NotSupportedException ();
			}
		}

		public static DataValue operator ^ (DataValue a, DataValue b)
		{
			if (a.IsNull)
			{
				return b;
			}
			if (b.IsNull)
			{
				return a;
			}
			a.AssertTypeMatch (b);
			if (a.IsNumber)
			{
				return new DataValue (a.AsNInt () ^ b.AsNInt ());
			}
			else if (a.IsBool)
			{
				return new DataValue (a.boolVal ^ b.boolVal);
			}
			else
			{
				throw new NotSupportedException ();
			}
		}

		public static bool operator == (DataValue left, DataValue right)
		{
			return left.Equals (right);
		}

		public static bool operator != (DataValue left, DataValue right)
		{
			return !(left == right);
		}

		public static implicit operator DataValue (decimal numberVal)
		{
			return new DataValue (numberVal);
		}
		public static implicit operator DataValue (decimal? numberVal)
		{
			return new DataValue (numberVal);
		}

		public static implicit operator DataValue (int intVal)
		{
			return new DataValue (intVal);
		}
		public static implicit operator DataValue (int? intVal)
		{
			return new DataValue (intVal);
		}

		public static implicit operator DataValue (bool boolVal)
		{
			return new DataValue (boolVal);
		}
		public static implicit operator DataValue (bool? boolVal)
		{
			return new DataValue (boolVal);
		}

		public static implicit operator DataValue (float floatVal)
		{
			return new DataValue (floatVal);
		}
		public static implicit operator DataValue (float? floatVal)
		{
			return new DataValue (floatVal);
		}

		public static implicit operator DataValue (Enum enumVal)
		{
			return new DataValue (enumVal);
		}

		public static implicit operator DataValue (string stringVal)
		{
			return new DataValue (stringVal);
		}

		public static implicit operator DataValue (double doubleVal)
		{
			return new DataValue (doubleVal);
		}
		public static implicit operator DataValue (double? doubleVal)
		{
			return new DataValue (doubleVal);
		}

		public static implicit operator bool (DataValue dataVal)
		{
			return dataVal.AsBool ();
		}

		public static implicit operator string (DataValue dataVal)
		{
			return dataVal.AsString ();
		}
		#endregion
		#region serialization
		[SerializeField] string _serializationString;

		void ISerializationCallbackReceiver.OnBeforeSerialize ()
		{
			switch (dataValueType.category)
			{
				case DataValueType.Categorys.Null:
					_serializationString = null;
					break;
				case DataValueType.Categorys.Number:
				case DataValueType.Categorys.Bool:
					_serializationString = numberVal.ToString ();
					break;
				case DataValueType.Categorys.Enum:
					_serializationString = enumVal.ToString ();
					break;
				case DataValueType.Categorys.String:
					_serializationString = stringVal;
					break;
			}
		}

		void ISerializationCallbackReceiver.OnAfterDeserialize ()
		{
			switch (dataValueType.category)
			{
				case DataValueType.Categorys.Null:
					numberVal = default;
					objVal = null;
					break;
				case DataValueType.Categorys.Number:
				case DataValueType.Categorys.Bool:
					numberVal = decimal.TryParse (_serializationString, out var val) ? val : default;
					objVal = null;
					break;
				case DataValueType.Categorys.Enum:
					numberVal = default;
					objVal = Enum.Parse (dataValueType.type, _serializationString);
					break;
				case DataValueType.Categorys.String:
					numberVal = default;
					stringVal = _serializationString;
					break;
			}
			_serializationString = null;
		}
		#endregion
	}

	#region combiner
	public delegate DataValue DataValueCombiner (DataValue a, DataValue b);

	public enum CombineTypes
	{
		Add,
		Multiply,
		TakeMax,
		TakeMin,
		And,
		Or,
		Xor,

		Override,
		None,
	}

	public static class DataValueCombiners
	{
		//Data
		public static DataValue Add (DataValue a, DataValue b) => a + b;
		public static DataValue Multiply (DataValue a, DataValue b) => a * b;
		public static DataValue And (DataValue a, DataValue b) => a & b;
		public static DataValue Or (DataValue a, DataValue b) => a | b;
		public static DataValue XOr (DataValue a, DataValue b) => a ^ b;
		public static DataValue Override (DataValue a, DataValue b) => b.HasValue ? b : a;
		public static DataValue None (DataValue a, DataValue b) => a.HasValue ? a : b;

		public static DataValueCombiner GetCombiner (this CombineTypes combineType)
		{
			switch (combineType)
			{
				case CombineTypes.Add:
					return Add;
				case CombineTypes.Multiply:
					return Multiply;
				case CombineTypes.And:
					return And;
				case CombineTypes.Or:
					return Or;
				case CombineTypes.Xor:
					return XOr;
				case CombineTypes.None:
					return None;
				case CombineTypes.Override:
				default:
					return Override;
			}
		}

		public static DataValue CombineValue (this CombineTypes combineType, DataValue a, DataValue b)
		{
			return combineType.GetCombiner ().Invoke (a, b);
		}

		public static CombineTypes GetDefaultCombineType (this DataValueType dataValueType)
		{
			switch (dataValueType.category)
			{
				default:
				case DataValueType.Categorys.Null:
					return CombineTypes.None;
				case DataValueType.Categorys.Number:
					return CombineTypes.Add;
				case DataValueType.Categorys.Bool:
					return CombineTypes.And;
				case DataValueType.Categorys.Enum:
				case DataValueType.Categorys.String:
					return CombineTypes.Override;
			}
		}

		public static bool CanUseCombineType (this DataValueType dataValueType, CombineTypes combineType)
		{
			switch (dataValueType.category)
			{
				case DataValueType.Categorys.Null:
				case DataValueType.Categorys.Enum:
				case DataValueType.Categorys.String:
					switch (combineType)
					{
						case CombineTypes.Override:
						case CombineTypes.None:
							return true;
					}
					break;
				case DataValueType.Categorys.Number:
					switch (combineType)
					{
						case CombineTypes.Add:
						case CombineTypes.Multiply:
						case CombineTypes.TakeMax:
						case CombineTypes.TakeMin:
						case CombineTypes.And:
						case CombineTypes.Or:
						case CombineTypes.Xor:
						case CombineTypes.Override:
						case CombineTypes.None:
							return true;
					}
					break;
				case DataValueType.Categorys.Bool:
					switch (combineType)
					{
						case CombineTypes.And:
						case CombineTypes.Or:
						case CombineTypes.Xor:
						case CombineTypes.Override:
						case CombineTypes.None:
							return true;
					}
					break;
			}
			return false;
		}
	}
	#endregion
}